package com.ssy.lingxi.pay.utils;

import cn.hutool.json.JSONUtil;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
import com.github.binarywang.wxpay.util.SignUtils;
import jodd.util.StringUtil;
import org.springframework.util.CollectionUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

/**
 * 微信支付工具类
 * @author jw.chen
 */
public class WeChatPayUtil {
	// 微信返回成功
    public static final String SUCCESS = "SUCCESS";
    
    // 微信返回失败
    public static final String FAIL = "FAIL";

    // 小程序客户端
    public static final String JS_API = "JSAPI";
    
    // 扫码支付
    public static final String NATIVE = "NATIVE";

    // H5调用
    public static final String MWEB = "MWEB";

	// 签名类型
	public static final String SIGN_TYPE = "MD5";

    // 获取openid需要的参数
    public static final String GRANT_TYPE = "authorization_code";
    
    // 移动端调用
    public static final String APP = "APP";
    
    // 场景信息(H5支付需要)
    public static final String SCENE_INFO = "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"http://www.shushangyun.com\",\"wap_name\": \"数商云\"}}";

    //小程序登录接口
	public static final String loginUrl = "https://api.weixin.qq.com/sns/jscode2session";

    //统一下单接口链接
	public static final String payUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    //查询订单接口链接
	public static final String orderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";

    //申请退款接口链接
	public static final String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";

    //微信回调入口(相对路径)
	public static final String notifyUrl = "/pay/wechat/notify";

	// 微信调用说明成功
	private static Map<String, String> errorMap = new HashMap<String, String>();
    static {
        errorMap.put("INVALID_REQUEST", "参数错误");
        errorMap.put("NOAUTH", "商户无此接口权限");
        errorMap.put("NOTENOUGH", "余额不足");
        errorMap.put("ORDERPAID", "商户订单已支付");
        errorMap.put("ORDERCLOSED", "订单已关闭");
        errorMap.put("SYSTEMERROR", "系统错误");
        errorMap.put("APPID_NOT_EXIST", "APPID不存在");
        errorMap.put("MCHID_NOT_EXIST", "MCHID不存在");
        errorMap.put("APPID_MCHID_NOT_MATCH", "appid和mch_id不匹配");
        errorMap.put("LACK_PARAMS", "缺少参数");
        errorMap.put("OUT_TRADE_NO_USED", "商户订单号重复");
        errorMap.put("SIGNERROR", "签名错误");
        errorMap.put("XML_FORMAT_ERROR", "XML格式错误");
        errorMap.put("REQUIRE_POST_METHOD", "请使用post方法");
        errorMap.put("POST_DATA_EMPTY", "post数据为空");
        errorMap.put("NOT_UTF8", "编码格式错误");
    }
    public static String getErrorMsg(String code) {
        return errorMap.get(code);
    }

    /**
     * 随机数生成算法
     */
    public static String getNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
	 * 微信参数签名
     */
    @SuppressWarnings("unchecked")
    public static String sign(Object request,String signType,String secretKey) {
        String json = JSONUtil.toJsonStr(request);
        Map<String, String> params = JSONUtil.toBean(json, Map.class);
		String totalFee = String.valueOf(params.get("totalFee"));
        if(StringUtil.isNotEmpty(totalFee) && !"null".equals(totalFee)){
			params.put("totalFee", totalFee);  //需要转成string,不然报错
		}
		String refundFee = String.valueOf(params.get("refundFee"));
        if(StringUtil.isNotEmpty(refundFee) && !"null".equals(refundFee)){
			params.put("refundFee", refundFee);  //需要转成string,不然报错
		}
        return SignUtils.createSign(params, signType, secretKey, null);
    }
    /**
	 * 根据返回对象，组装返回前端结果
	 * 微信内H5调起支付 调用
     */
    public static HashMap<String, String> packageResult(WxPayUnifiedOrderResult result,String signType,String secretKey) {
    	String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);	//交易结束时间
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("appId", result.getAppid());
        data.put("timeStamp", timeStamp);
        data.put("nonceStr", result.getNonceStr());
        data.put("package", "prepay_id=" + result.getPrepayId());
        data.put("signType", signType);
        String paySign = SignUtils.createSign(data, signType, secretKey, null);
        ;
        data.put("paySign", paySign);
        return data;
    }

    /**
	 * 根据返回对象，组装返回前端结果
     * APP端调起支付 调用
     */
    public static HashMap<String, String> packageAppResult(WxPayUnifiedOrderResult result, String timeStamp,String mchId,String signType,String secretKey) {
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("appId", result.getAppid());
        data.put("partnerid", mchId);
        data.put("timeStamp", timeStamp);
        data.put("nonceStr", result.getNonceStr());
        data.put("prepayid", result.getPrepayId());
        data.put("package", "Sign=WXPay");
        data.put("signType", signType);
        String sign = SignUtils.createSign(data,signType,secretKey, null);
        data.put("sign", sign);
        return data;
    }
    
	/**
	 * 微信支付请求
	 * @param requestUrl 支付网关Url
	 * @param requestMethod 请求方法
	 * @param outputStr
	 * @return
	 */
	public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
		try {
			URL url = new URL(requestUrl);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setUseCaches(false);
			// 设置请求方式（GET/POST）
			conn.setRequestMethod(requestMethod);
			conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
			// 当outputStr不为null时向输出流写数据
			if (null != outputStr) {
				OutputStream outputStream = conn.getOutputStream();
				// 注意编码格式
				outputStream.write(outputStr.getBytes(StandardCharsets.UTF_8));
				outputStream.close();
			}
			// 从输入流读取返回内容
			InputStream inputStream = conn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String str;
			StringBuffer buffer = new StringBuffer();
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			// 释放资源
			bufferedReader.close();
			inputStreamReader.close();
			inputStream.close();
			conn.disconnect();
			return buffer.toString();
		} catch (ConnectException ce) {
			System.out.println("连接超时：" + ce);
		} catch (Exception e) {
			System.out.println("https请求异常" + e);
		}
		return null;
	}

	/**
	 * 返回给微信告知异步通知是否成功
	 * @param response Http响应
	 * @param flag 是否成功标记
	 * @return 通知结果
	 */
	public static String returnWeChatNotify(HttpServletResponse response, boolean flag){
		String resXml = "";
		try {
			if (flag) {
				resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
			} else {
				resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>";
			}
			response.setContentType("text/xml");
			BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
			out.write(resXml.getBytes());
			out.flush();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	    return resXml;
	}
    
	/**
	 * 接收微信异步通知
	 * @param request Http请求
	 * @throws IOException IO异常
	 */
	public static String reciverWx(HttpServletRequest request) throws IOException {
		InputStream inputStream = request.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
		StringBuilder sb = new StringBuilder();
        String s;
        while ((s = reader.readLine()) != null) {
            sb.append(s);
        }
        reader.close();
        inputStream.close();
		return sb.toString();
	}
	
	/**
	 * 接收微信异步通知
	 */
	public static void returnWx(HttpServletResponse response, boolean flag) {
		String resXml = "";
		if (flag) {
			resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
		} else {
			resXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>";
		}
    	PrintWriter writer;
		try {
			writer = response.getWriter();
			writer.write(resXml);
			writer.flush();
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 将封装好的参数转换成Xml格式类型的字符串
	 * @param parameters 预支付所需参数
	 */
	public static String getRequestXml(Map<String, String> parameters) {
		StringBuffer sb = new StringBuffer();
		sb.append("<xml>");
		Set<String> set = parameters.keySet();
		for (String k : set) {
			String v = parameters.get(k);
			if ("sign".equalsIgnoreCase(k)) {

			} else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)) {
				sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
			} else {
				sb.append("<" + k + ">" + v + "</" + k + ">");
			}
		}
		sb.append("<" + "sign" + ">" + "<![CDATA[" + parameters.get("sign") + "]]></" + "sign" + ">");
		sb.append("</xml>");
		return sb.toString();
	}

	/**
	 * XML格式字符串转换为Map
	 * @param strXML XML字符串
	 * @return XML数据转换后的Map
	 */
	public static Map<String, String> xmlToMap(String strXML) throws Exception {
		Map<String, String> data = new HashMap<String, String>();
		DocumentBuilder documentBuilder = newDocumentBuilder();
		InputStream stream = new ByteArrayInputStream(strXML.getBytes(StandardCharsets.UTF_8));
		Document doc = documentBuilder.parse(stream);
		doc.getDocumentElement().normalize();
		NodeList nodeList = doc.getDocumentElement().getChildNodes();
		for (int idx = 0; idx < nodeList.getLength(); ++idx) {
			Node node = nodeList.item(idx);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				org.w3c.dom.Element element = (org.w3c.dom.Element) node;
				data.put(element.getNodeName(), element.getTextContent());
			}
		}
		stream.close();
		return data;
	}

	/**
	 * XML格式字符串转换为Map,然后通过key获取对应的值
	 * @param strXML XML字符串
	 * @param key 要获取的Key
	 */
	public static String getXmlByKey(String strXML, String key) throws Exception {
		Map<String, String> map = xmlToMap(strXML);
		return CollectionUtils.isEmpty(map) ? null : map.get(key);
	}

	/**
	 * 将Map转换为XML格式的字符串
	 * @param data Map类型数据
	 * @return XML格式的字符串
	 */
	public static String mapToXml(Map<String, String> data) throws Exception {
		Document document = newDocument();
		org.w3c.dom.Element root = document.createElement("xml");
		document.appendChild(root);
		for (String key : data.keySet()) {
			String value = data.get(key);
			if (value == null) {
				value = "";
			}
			value = value.trim();
			org.w3c.dom.Element filed = document.createElement(key);
			filed.appendChild(document.createTextNode(value));
			root.appendChild(filed);
		}
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer transformer = tf.newTransformer();
		DOMSource source = new DOMSource(document);
		transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		StringWriter writer = new StringWriter();
		StreamResult result = new StreamResult(writer);
		transformer.transform(source, result);
		String output = writer.getBuffer().toString(); // .replaceAll("\n|\r", "");
		try {
			writer.close();
		} catch (Exception ignored) {
		}
		return output;
	}

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。
	 * @return 签名是否正确
	 */
	public static boolean isSignValid(Map<String, String> parameters, String privateKey, String character) throws Exception {
		if (!parameters.containsKey("sign")) {
			return false;
		}
		String sign = parameters.get("sign");
		return createSign(parameters, privateKey, character).equals(sign);
	}

	/**
	 * 创建签名
	 * 定义签名，微信根据参数字段的ASCII码值进行排序 加密签名,故使用stream进行参数排序
	 */
	public static String createSign(Map<String, String> parameters, String privateKey, String character) throws Exception {
		StringBuffer sb = new StringBuffer();
		Set<String> set = parameters.keySet();
		set.stream().sorted().forEach(k -> {
			String v = parameters.get(k);
			if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k + "=" + v + "&");
			}
		});
		// 最后加密时添加商户密钥，由于key值放在最后，所以不用添加到SortMap里面去，单独处理，编码方式采用UTF-8
		sb.append("key=" + privateKey);
		System.out.println(sb.toString());
		System.out.println(MD5(sb.toString(), character).toUpperCase());
		return MD5(sb.toString(), character).toUpperCase();
	}

	/**
	 * 随机字符串生成
	 */
	public static String getRandomString(int length) {
		String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		Random random = new Random();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		return sb.toString();
	}

	/**
	 * 生成 MD5
	 */
	public static String MD5(String origin, String charsetname) throws Exception {
		MessageDigest md = MessageDigest.getInstance("MD5");
		byte[] array = null;
		if (charsetname == null || "".equals(charsetname)) {
			array = md.digest(origin.getBytes());
		} else {
			array = md.digest(origin.getBytes(charsetname));
		}
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 创建签名
	 * 定义签名，微信根据参数字段的ASCII码值进行排序 加密签名,故使用stream进行参数排序
	 */
	public static String createAppSign(Map<String, String> parameters, String privateKey, String character) throws Exception {
		StringBuffer sb = new StringBuffer();
		Set<String> set = parameters.keySet();
		set.stream().sorted().forEach(k -> {
			String v = parameters.get(k);
			if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k).append("=").append(v).append("&");
			}
		});
		// 最后加密时添加商户密钥，由于key值放在最后，所以不用添加到SortMap里面去，单独处理，编码方式采用UTF-8
		sb.append("key=").append(privateKey);
		return HMACSHA256(sb.toString(), privateKey).toUpperCase();
	}

	/**
	 * 生成 HMACSHA256
	 * @param data 待处理数据
	 * @param key  密钥
	 * @return 加密结果
	 */
	public static String HMACSHA256(String data, String key) throws Exception {
		Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
		SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
		sha256_HMAC.init(secret_key);
		byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 返回客户端ip
	 * @param request Http请求
	 * @return ip字符串
	 */
	public static String getRemoteAddrIp(HttpServletRequest request) {
		String ip = request.getHeader("X-Forwarded-For");
		if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			// 多次反向代理后会有多个ip值，第一个ip才是真实ip
			int index = ip.indexOf(",");
			if (index != -1) {
				return ip.substring(0, index);
			} else {
				return ip;
			}
		}
		ip = request.getHeader("X-Real-IP");
		if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			return ip;
		}
		return request.getRemoteAddr();
	}
	
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }


	private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
			"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
	/**
	 * 微信支付签名算法sign
	 * @param parameters 参数集合
	 * @return 返回md5签名
	 */
	@SuppressWarnings("unchecked")
	public static String createWxPaySign(String signKey,SortedMap<Object,Object> parameters) {
		StringBuilder sb = new StringBuilder();       // 多线程访问的情况下需要用StringBuffer
		Set es = parameters.keySet();                 // 所有参与传参的key按照accsii排序（升序）
		for (Object set : es) {
			String k = set.toString();
			Object v = parameters.get(k);
			sb.append(k)
					.append("=")
					.append(v.toString())
					.append("&");
		}
		sb.append("key=")
				.append(signKey);
		return str2MD5(sb.toString(), "utf-8").toUpperCase();
	}

	/** MD5加密
	 * @param data 要加密的数据
	 * @param encode 加密的编码
	 * @return md5字符串
	 */
	public static String str2MD5(String data, String encode) {
		String resultString = null;
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			if (encode == null || "".equals(encode))
				resultString = byteArrayToHexString(md.digest(data
						.getBytes()));
			else {
				resultString = byteArrayToHexString(md.digest(data
						.getBytes(encode)));
			}
		} catch (Exception exception) {
		}
		return resultString;
	}

	/** byte数组转换16进制字符串
	 * @param b 要转换的byte数组
	 * @return 16进制字符串
	 */
	private static String byteArrayToHexString(byte b[]) {
		StringBuffer resultSb = new StringBuffer();
		for (int i = 0; i < b.length; i++)
			resultSb.append(byteToHexString(b[i]));

		return resultSb.toString();
	}

	/** byte转换成16进制字符串
	 * @param b 要转换的byte
	 * @return byte对应的16进制字符串
	 */
	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0)
			n += 256;
		int d1 = n / 16;
		int d2 = n % 16;
		return hexDigits[d1] + hexDigits[d2];
	}

	/** map集合参数转换为xml格式
	 * @param dataMap 要转换的map对象
	 * @return XML格式的字符串
	 */
	private String map2XML(SortedMap<Object, Object> dataMap)
	{
		synchronized (WeChatPayUtil.class)                             // 不是多线程访问可以不用加锁
		{
			StringBuilder strBuilder = new StringBuilder();
			Set<Object> objSet = dataMap.keySet();
			strBuilder.append("<xml>");
			for (Object key : objSet)
			{
				if (key == null)
				{
					continue;
				}
				Object value = dataMap.get(key);
				strBuilder.append("<")
						.append(key.toString())
						.append(">")
						.append(value)
						.append("</")
						.append(key.toString())
						.append(">\n");
			}
			strBuilder.append("</xml>");
			return strBuilder.toString();
		}
	}

	/** 生成随机数
	 * @param count 要生成的随机数位数
	 * @return 随机数字符串
	 */
	private String createNonceStr(int count){
		String[] nums = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
		int maxIndex = nums.length - 1;
		int numIndex;
		StringBuilder builder = new StringBuilder(count);
		for (int i = 0; i < count; i++){
			numIndex = (int)(Math.random() * maxIndex);
			builder.append(nums[numIndex]);
		}
		return builder.toString();
	}

}
